﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace ArgusControls
{
	[TemplatePart(Name = PartNameResizeAreaN, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameResizeAreaNE, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameResizeAreaE, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameResizeAreaSE, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameResizeAreaS, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameResizeAreaSW, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameResizeAreaW, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameResizeAreaNW, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameDragArea, Type = typeof(UIElement))]
	[TemplatePart(Name = PartNameClientArea, Type = typeof(FrameworkElement))]
	public class FloatingControl : ContentControl
	{
		#region Part Names
		protected enum PartName : byte
		{
			None,
			ResizeAreaN,
			ResizeAreaNE,
			ResizeAreaE,
			ResizeAreaSE,
			ResizeAreaS,
			ResizeAreaSW,
			ResizeAreaW,
			ResizeAreaNW,
			DragArea
		}
		private const string PartNameResizeAreaN = "ResizeAreaN";
		private const string PartNameResizeAreaNE="ResizeAreaNE";
		private const string PartNameResizeAreaE="ResizeAreaE";
		private const string PartNameResizeAreaSE="ResizeAreaSE";
		private const string PartNameResizeAreaS="ResizeAreaS";
		private const string PartNameResizeAreaSW="ResizeAreaSW";
		private const string PartNameResizeAreaW="ResizeAreaW";
		private const string PartNameResizeAreaNW="ResizeAreaNW";
		private const string PartNameDragArea = "DragArea";
		private const string PartNameClientArea = "ClientArea";
		#endregion

		#region Parts
		private UIElement PartResizeAreaN;
		private UIElement PartResizeAreaNE;
		private UIElement PartResizeAreaE;
		private UIElement PartResizeAreaSE;
		private UIElement PartResizeAreaS;
		private UIElement PartResizeAreaSW;
		private UIElement PartResizeAreaW;
		private UIElement PartResizeAreaNW;
		private UIElement PartDragArea;
		private FrameworkElement PartClientArea;
		#endregion

		#region Properties
		private UIElement UIParent { get { return this.Parent as UIElement; } }

		public Rect BoundaryRect
		{
			get
			{
				return new Rect()
				{
					X = this.Margin.Left,
					Y = this.Margin.Top,
					Width = this.Width,
					Height = this.Height
				};
			}
			set
			{
				this.Margin = new Thickness(value.X, value.Y, 0, 0);
				this.Width = value.Width;
				this.Height = value.Height;
			}
		}

		public Size Size
		{
			get { return new Size(this.ActualWidth, this.ActualHeight); }
			set
			{
				if (double.IsNaN(value.Width) == false || double.IsNaN(value.Height) == false)
					this.ClientSize = new Size(double.NaN, double.NaN);
				this.Width = value.Width;
				this.Height = value.Height;
			}
		}

		private static readonly DependencyProperty ClientWidthProperty = DependencyProperty.Register("ClientWidth", typeof(double), typeof(FloatingControl), new PropertyMetadata(double.NaN));
		public double ClientWidth
		{
			get { return (double)this.GetValue(ClientWidthProperty); }
			set { this.SetValue(ClientWidthProperty, value); }
		}

		private static readonly DependencyProperty ClientHeightProperty = DependencyProperty.Register("ClientHeight", typeof(double), typeof(FloatingControl), new PropertyMetadata(double.NaN));
		public double ClientHeight
		{
			get { return (double)this.GetValue(ClientHeightProperty); }
			set { this.SetValue(ClientHeightProperty, value); }
		}

		public Size ClientSize
		{
			get
			{
				return new Size(this.ClientWidth, this.ClientHeight);
			}
			set
			{
				if (double.IsNaN(value.Width) == false || double.IsNaN(value.Height) == false)
					this.Size = new Size(double.NaN, double.NaN);
				this.ClientWidth = value.Width;
				this.ClientHeight = value.Height;
			}
		}
		#endregion

		#region Fields
		PartName activePart;
		Point startPoint;
		Thickness startMargin;
		double startWidth;
		double startHeight;
		#endregion

		public FloatingControl()
			//: base()
		{
			this.activePart = PartName.None;
			this.DefaultStyleKey = typeof(FloatingControl);
		}

		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();
			if (this.PartDragArea != null)
				this.PartDragArea.MouseLeftButtonDown -= PartDragArea_MouseLeftButtonDown;
			if (this.PartResizeAreaE != null)
				this.PartResizeAreaE.MouseLeftButtonDown -= PartResizeAreaE_MouseLeftButtonDown;
			if (this.PartResizeAreaN != null)
				this.PartResizeAreaN.MouseLeftButtonDown -= PartResizeAreaN_MouseLeftButtonDown;
			if (this.PartResizeAreaNE != null)
				this.PartResizeAreaNE.MouseLeftButtonDown -= PartResizeAreaNE_MouseLeftButtonDown;
			if (this.PartResizeAreaNW != null)
				this.PartResizeAreaNW.MouseLeftButtonDown -= PartResizeAreaNW_MouseLeftButtonDown;
			if (this.PartResizeAreaS != null)
				this.PartResizeAreaS.MouseLeftButtonDown -= PartResizeAreaS_MouseLeftButtonDown;
			if (this.PartResizeAreaSE != null)
				this.PartResizeAreaSE.MouseLeftButtonDown -= PartResizeAreaSE_MouseLeftButtonDown;
			if (this.PartResizeAreaSW != null)
				this.PartResizeAreaSW.MouseLeftButtonDown -= PartResizeAreaSW_MouseLeftButtonDown;
			if (this.PartResizeAreaW != null)
				this.PartResizeAreaW.MouseLeftButtonDown -= PartResizeAreaW_MouseLeftButtonDown;
			if (this.PartClientArea != null)
				this.PartClientArea.SizeChanged -= PartClientArea_SizeChanged;

			this.PartDragArea = this.GetTemplateChild(PartNameDragArea) as UIElement;
			this.PartResizeAreaN = this.GetTemplateChild(PartNameResizeAreaN) as UIElement;
			this.PartResizeAreaNE = this.GetTemplateChild(PartNameResizeAreaNE) as UIElement;
			this.PartResizeAreaE = this.GetTemplateChild(PartNameResizeAreaE) as UIElement;
			this.PartResizeAreaSE = this.GetTemplateChild(PartNameResizeAreaSE) as UIElement;
			this.PartResizeAreaS = this.GetTemplateChild(PartNameResizeAreaS) as UIElement;
			this.PartResizeAreaSW = this.GetTemplateChild(PartNameResizeAreaSW) as UIElement;
			this.PartResizeAreaW = this.GetTemplateChild(PartNameResizeAreaW) as UIElement;
			this.PartResizeAreaNW = this.GetTemplateChild(PartNameResizeAreaNW) as UIElement;
			this.PartClientArea = this.GetTemplateChild(PartNameClientArea) as FrameworkElement;

			if (this.PartDragArea != null)
				this.PartDragArea.MouseLeftButtonDown += PartDragArea_MouseLeftButtonDown;
			if (this.PartResizeAreaE != null)
				this.PartResizeAreaE.MouseLeftButtonDown += PartResizeAreaE_MouseLeftButtonDown;
			if (this.PartResizeAreaN != null)
				this.PartResizeAreaN.MouseLeftButtonDown += PartResizeAreaN_MouseLeftButtonDown;
			if (this.PartResizeAreaNE != null)
				this.PartResizeAreaNE.MouseLeftButtonDown += PartResizeAreaNE_MouseLeftButtonDown;
			if (this.PartResizeAreaNW != null)
				this.PartResizeAreaNW.MouseLeftButtonDown += PartResizeAreaNW_MouseLeftButtonDown;
			if (this.PartResizeAreaS != null)
				this.PartResizeAreaS.MouseLeftButtonDown += PartResizeAreaS_MouseLeftButtonDown;
			if (this.PartResizeAreaSE != null)
				this.PartResizeAreaSE.MouseLeftButtonDown += PartResizeAreaSE_MouseLeftButtonDown;
			if (this.PartResizeAreaSW != null)
				this.PartResizeAreaSW.MouseLeftButtonDown += PartResizeAreaSW_MouseLeftButtonDown;
			if (this.PartResizeAreaW != null)
				this.PartResizeAreaW.MouseLeftButtonDown += PartResizeAreaW_MouseLeftButtonDown;
			if (this.PartClientArea != null)
				this.PartClientArea.SizeChanged += PartClientArea_SizeChanged;
		}

		void PartClientArea_SizeChanged(object sender, SizeChangedEventArgs e)
		{
			this.ClientWidth = e.NewSize.Width;
			this.ClientHeight = e.NewSize.Height;
		}

		#region Part ButtonDownEventHandlers
		void PartResizeAreaW_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.ResizeAreaW, e);
		}

		void PartResizeAreaSW_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.ResizeAreaSW, e);
		}

		void PartResizeAreaSE_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.ResizeAreaSE, e);
		}

		void PartResizeAreaS_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.ResizeAreaS, e);
		}

		void PartResizeAreaNW_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.ResizeAreaNW, e);
		}

		void PartResizeAreaNE_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.ResizeAreaNE, e);
		}

		void PartResizeAreaN_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.ResizeAreaN, e);
		}

		void PartResizeAreaE_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.ResizeAreaE, e);
		}

		void PartDragArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			this.OnPartMouseLeftButtonDown(PartName.DragArea, e);
		}
		#endregion

		protected virtual void OnPartMouseLeftButtonDown(PartName Part, MouseButtonEventArgs e)
		{
			UIElement ParentUIElement = this.UIParent;
			if (ParentUIElement == null)
				return;
			if (Application.Current.RootVisual == null)
				return;

			if (this.activePart != PartName.None && Part != PartName.None)
				return;
			
			this.activePart = Part;
			this.startPoint = e.GetPosition(ParentUIElement);
			this.startMargin = this.Margin;
			this.startWidth = this.ActualWidth;
			this.startHeight = this.ActualHeight;
			ParentUIElement.MouseMove -= OnUIParentMouseMove;
			ParentUIElement.MouseMove += OnUIParentMouseMove;
			//ParentUIElement.RemoveHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.OnRootVisualMouseLeftButtonUp));
			//ParentUIElement.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.OnRootVisualMouseLeftButtonUp), true);
			Application.Current.RootVisual.RemoveHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.OnRootVisualMouseLeftButtonUp));
			Application.Current.RootVisual.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.OnRootVisualMouseLeftButtonUp), true);
		}

		protected virtual void OnRootVisualMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			this.activePart = PartName.None;
			if (this.UIParent != null)
				this.UIParent.MouseMove -= this.OnUIParentMouseMove;
			if (Application.Current.RootVisual != null)
				Application.Current.RootVisual.RemoveHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.OnRootVisualMouseLeftButtonUp));
		}

		protected virtual void OnUIParentMouseMove(object sender, MouseEventArgs e)
		{
			if (this.activePart == PartName.None)
				return;

			UIElement parent = this.UIParent;
			Point p = e.GetPosition(parent);
			double dx = p.X - this.startPoint.X;
			double dy = p.Y - this.startPoint.Y;
			double minWidth = this.BorderThickness.Left + this.BorderThickness.Right;
			double minHeight = this.BorderThickness.Top + this.BorderThickness.Bottom;
			FrameworkElement content = this.Content as FrameworkElement;
			if (content != null)
			{
				minWidth += content.MinWidth;
				minHeight += content.MinHeight;
			}

			//if (parent.RenderSize.ToRectangle(new Point(0, 0), System.Windows.ExtensionMethods.RectOption.TopLeft).Contains(p) == false)
			//	return;

			if (this.activePart == PartName.DragArea)
			{
				Thickness m = this.Margin;
				m.Left = this.startMargin.Left + dx;
				m.Top = this.startMargin.Top + dy;
				this.Margin = m;
			}
			else if (this.activePart == PartName.ResizeAreaE && this.startWidth + dx >= minWidth)
			{
				this.Width = this.startWidth + dx;
			}
			else if (this.activePart == PartName.ResizeAreaN && this.startHeight - dy >= minHeight)
			{
				Thickness m = this.Margin;
				m.Top = this.startMargin.Top + dy;
				this.Margin = m;
				this.Height = this.startHeight - dy;
			}
			else if (this.activePart == PartName.ResizeAreaNE && this.startWidth + dx >= minWidth && this.startHeight - dy >= minHeight)
			{
				Size s = new Size(this.startWidth + dx, this.startHeight - dy);
				s = s.ChangeRatio((new Size(this.Width, this.Height)).GetRatio(), System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize);
				dy = this.startHeight - s.Height;
				Thickness m = this.Margin;
				m.Top = this.startMargin.Top + dy;
				this.Margin = m;
				this.Width = s.Width;
				this.Height = s.Height;
			}
			else if (this.activePart == PartName.ResizeAreaNW && this.startWidth - dx >= minWidth && this.startHeight - dy >= minHeight)
			{
				Size s = new Size(this.startWidth - dx, this.startHeight - dy);
				s = s.ChangeRatio((new Size(this.Width, this.Height)).GetRatio(), System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize);
				dx = this.startWidth - s.Width;
				dy = this.startHeight - s.Height;
				Thickness m = this.Margin;
				m.Top = this.startMargin.Top + dy;
				m.Left = this.startMargin.Left + dx;
				this.Margin = m;
				this.Width = s.Width;
				this.Height = s.Height;
			}
			else if (this.activePart == PartName.ResizeAreaS && this.startHeight + dy >= minHeight)
			{
				this.Height = this.startHeight + dy;
			}
			else if (this.activePart == PartName.ResizeAreaSE && this.startWidth + dx >= minWidth && this.startHeight + dy >= minHeight)
			{
				Size s = new Size(this.startWidth + dx, this.startHeight + dy);
				s = s.ChangeRatio((new Size(this.Width, this.Height)).GetRatio(), System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize);
				this.Width = s.Width;
				this.Height = s.Height;
			}
			else if (this.activePart == PartName.ResizeAreaSW && this.startWidth - dx >= minWidth && this.startHeight + dy >= minHeight)
			{
				Size s = new Size(this.startWidth - dx, this.startHeight + dy);
				s = s.ChangeRatio((new Size(this.Width, this.Height)).GetRatio(), System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize);
				dx = this.startWidth - s.Width;
				Thickness m = this.Margin;
				m.Left = this.startMargin.Left + dx;
				this.Margin = m;
				this.Width = s.Width;
				this.Height = s.Height;
			}
			else if (this.activePart == PartName.ResizeAreaW && this.startWidth - dx >= minWidth)
			{
				Thickness m = this.Margin;
				m.Left = this.startMargin.Left + dx;
				this.Margin = m;
				this.Width = this.startWidth - dx;
			}
		}
	}
}
